﻿#include "Decoder.h"
#include <fstream>

Decoder::Decoder()
{
}

Decoder::Decoder(int width, int height, QuantizationMatrix *qm, int columnLength)
{
    initDCTMatrices();
    _quantizationMatrix = qm;
    _imageMatrix.setSize(columnLength);
    _width = width;
    _height = height;
    int horBlocks = _width/columnLength;
    int vertBlocks = _height/columnLength;
    _totalBlocks = horBlocks*vertBlocks;
    _columnLength = columnLength;
    _motionCompensation = 0;
}

Decoder::Decoder(DecoderSettings& config, QuantizationMatrix *qm, int columnLength)
{
    initDCTMatrices();
    _quantizationMatrix = qm;
    _imageMatrix.setSize(columnLength);
    _width = config.width();
    _height = config.height();
    int horBlocks = _width/columnLength;
    int vertBlocks = _height/columnLength;
    _totalBlocks = horBlocks*vertBlocks;
    _columnLength = columnLength;
    _motionCompensation = config.getMotionCompensation();
}

uint8_t* Decoder::decode( BitStreamReader& bsr)
{
    _decompressedData.clear();

	double** rleList = new double*[_totalBlocks];

	for(int i = 0; i < _totalBlocks; ++i)
	{
		rleList[i] = performRLDecoding(bsr);
	}

	_decompressedData.resize(_totalBlocks);

    uint8_t* decodedData;

#if I_DEC_PARALLEL == 1
#pragma omp parallel for
#endif
    for(int i = 0; i < _totalBlocks; ++i)
    {
		Matrix m(_columnLength);
        
        performDeZigZag(rleList[i], m);
        performDeQuantization(m);
        performIDCT(m);
        m.deNormalize();
        _decompressedData.set(i, m);
    }

    decodedData = _decompressedData.mergeBlocks(_width,_height, _columnLength);

    return decodedData;
}

uint8_t* Decoder::decodePFrame(BitStreamReader& bsr, uint8_t* previousFrame)
{
    _decompressedData.clear();
    uint8_t* decodedData;
    Decoder decoder(_columnLength, _columnLength, _quantizationMatrix, 4);
    for(int i = 0; i < _totalBlocks; ++i)
    {
        uint32_t type = bsr.get(2);

        if(type == 0){
            int8_t x = (int8_t) bsr.get(8);
            int8_t y = (int8_t) bsr.get(8);

            Matrix* temp = BlockList::getBlockAtCoord( ((i*_columnLength)%_width) + x, (((i*_columnLength)/_width)*_columnLength) + y, previousFrame, _width, _height, 16);
            _imageMatrix = *(temp);
            delete temp;

        } else if(type == 1){

            uint32_t testX = bsr.get(8);
            uint32_t testY = bsr.get(8);

            int8_t x = (int8_t) testX;
            int8_t y = (int8_t) testY;

            if(x != 0)
            {
                x = x*2;
                x = x/2;
            }

            Matrix* temp = BlockList::getBlockAtCoord( ((i*_columnLength)%_width) + x, (((i*_columnLength)/_width)*_columnLength) + y, previousFrame, _width, _height, 16);
            _imageMatrix = *(temp);
            delete temp;

            bsr.get_bit();

            uint32_t length = bsr.get(31);

            uint8_t* buffer = new uint8_t[length];
            for(int j=0; j < length; ++j){
                buffer[j] = (uint8_t) bsr.get(8);
            }
            util::BitStreamReader bsr2(buffer, length);

            uint8_t *buff = decoder.decode(bsr2);

			delete[] buffer;

            if(_motionCompensation == 1)
            {
                Matrix temp(_columnLength);
                temp.setMatrixRowMajor(buff);
                _imageMatrix = temp + _imageMatrix;
            }

            delete[] buff;

        } else if(type == 2){
            bsr.get_bit();

            uint32_t length = bsr.get(31);

            uint8_t* buffer = new uint8_t[length];
            for(int j=0; j < length; ++j){
                buffer[j] = (uint8_t) bsr.get(8);
            }
            util::BitStreamReader bsr2(buffer, length);

            uint8_t *buff = decoder.decode(bsr2);

			delete[] buffer;

            _imageMatrix.setMatrixRowMajor(buff);
            delete[] buff;
        }

        _decompressedData.append(_imageMatrix);
    }

    decodedData = _decompressedData.mergeBlocks(_width,_height, _columnLength);

    return decodedData;
}

double* Decoder::performRLDecoding( BitStreamReader& bsr )
{
    int readElements = 0;

	double* imageList = new double[16];

    int elementsInBlock = _columnLength*_columnLength;

    while(readElements < elementsInBlock)
    {
        //fstream logFile;
        //logFile.open("log.txt", fstream::out | fstream::app);
        uint8_t rleBit = bsr.get_bit();
        if(rleBit == 0)
        {
            uint32_t contentLength = bsr.get(3);
            int8_t signBit = bsr.get_bit();
            int8_t pixelValue = bsr.get(contentLength+1);

            if(signBit != 0)
            {
                pixelValue = -(pixelValue+1);
            }
            imageList[readElements] = pixelValue;
            ++readElements;

            //sprintf(buffer, "Num: %d O: %u NBits: %u\n", pixelValue, temp, contentLength+1);

            //logFile << buffer;

        }
        else
        {
            uint32_t repitionLength = bsr.get(4) + 1;
            for(uint32_t i = 0; i < repitionLength; ++i)
            {
                imageList[readElements] = 0;
                ++readElements;
            }

            //logFile << "Entering RLE\n";
        }

        //logFile.close();
    }

	return imageList;
}

void Decoder::performDeZigZag(double* imageList, Matrix& m)
{
    double imageMatrix[16];
    imageMatrix[0] = imageList[0];
    imageMatrix[1] = imageList[1];
    imageMatrix[2] = imageList[5];
    imageMatrix[3] = imageList[6];
    imageMatrix[4] = imageList[2];
    imageMatrix[5] = imageList[4];
    imageMatrix[6] = imageList[7];
    imageMatrix[7] = imageList[12];
    imageMatrix[8] = imageList[3];
    imageMatrix[9] = imageList[8];
    imageMatrix[10] = imageList[11];
    imageMatrix[11] = imageList[13];
    imageMatrix[12] = imageList[9];
    imageMatrix[13] = imageList[10];
    imageMatrix[14] = imageList[14];
    imageMatrix[15] = imageList[15];
    m.setMatrixRowMajor(imageMatrix);
}

void Decoder::performDeQuantization(Matrix& m)
{
    m = m * (*_quantizationMatrix);
}

void Decoder::performIDCT(Matrix& m)
{
    m = (_tDCTMatrix * m) * _DCTMatrix;
}

void Decoder::initDCTMatrices()
{
    _tDCTMatrix.setSize(4);
    _DCTMatrix.setSize(4);
    double DCTMatrix[16];
    double tDCTMatrix[16];

    DCTMatrix[0] = 0.5;
    DCTMatrix[1] = 0.5;
    DCTMatrix[2] = 0.5;
    DCTMatrix[3] = 0.5;
    DCTMatrix[4] = 0.653;
    DCTMatrix[5] = 0.271;
    DCTMatrix[6] = -0.271;
    DCTMatrix[7] = -0.653;
    DCTMatrix[8] = 0.5;
    DCTMatrix[9] = -0.5;
    DCTMatrix[10] = -0.5;
    DCTMatrix[11] = 0.5;
    DCTMatrix[12] = 0.271;
    DCTMatrix[13] = -0.653;
    DCTMatrix[14] = 0.653;
    DCTMatrix[15] = -0.271;

    _DCTMatrix.setMatrixRowMajor(DCTMatrix);

    tDCTMatrix[0] = 0.5;
    tDCTMatrix[1] = 0.653;
    tDCTMatrix[2] = 0.5;
    tDCTMatrix[3] = 0.271;
    tDCTMatrix[4] = 0.5;
    tDCTMatrix[5] = 0.271;
    tDCTMatrix[6] = -0.5;
    tDCTMatrix[7] = -0.653;
    tDCTMatrix[8] = 0.5;
    tDCTMatrix[9] = -0.271;
    tDCTMatrix[10] = -0.5;
    tDCTMatrix[11] = 0.653;
    tDCTMatrix[12] = 0.5;
    tDCTMatrix[13] = -0.653;
    tDCTMatrix[14] = 0.5;
    tDCTMatrix[15] = -0.271;

    _tDCTMatrix.setMatrixRowMajor(tDCTMatrix);

}

/*void Decoder::initQuantMatrixFromFile(string quantFilename)
{
    fstream quantFile;
    quantFile.open(quantFilename.data(), fstream::in);
    char buffer[64];
    for(int i = 0; i < 16; i+=4)
    {
        quantFile.getline(buffer, 64);
        sscanf(buffer, "%d %d %d %d", &(quantizationMatrix[i]),&(quantizationMatrix[i+1]),&(quantizationMatrix[i+2]),&(quantizationMatrix[i+3]));
    }
}

void Decoder::multiplyMatrix(float* first, float* second, float* resultStorage)
{
    for(int i = 0; i < 16; ++i)
    {
        resultStorage[i] = calculateMatrixMultElement(i, first, second);
    }
}

float Decoder::calculateMatrixMultElement(int pos, float* first, float* second)
{
    float result = 0;
    int row = pos/4;
    int col = pos%4;
    for(int i = 0; i < 4; ++i){
        result += (first[4*row+i] * second[col+i*4]);
    }
    return result;
}*/